package bloodfang;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Attacks {
	
	double constant_scaling = 67.; // for lvl 90 / lvl100 = 261 
	Stats s;
	
	public Attacks(Stats stats) {
		this.s = stats;
	}

	public AttackData get_dmg_sinister_strike(){
		Weapon w = s.getMh();
		double damage = w.getSpeed() * w.getDps() + w.getType().getNormalizedSpeed() * s.getBuffed(Stat.ap) / 3.5;
		double mod = 0.85;
		damage = damage * mod;
		// t14 2pc
		if(s.hasSetBonus(SetBonus.T14_2PC)){
			damage *= 1.15;
		}
		// perk improved sinister strike
		if(s.getSettings().spec == Spec.COMBAT && s.getSettings().player_level >= 100)
			damage *= 1.2;
		double crit_damage = damage * this.getCritMultiplier();
		return new AttackData("sinister_strike",AttackType.PHYSICAL, damage, crit_damage, get_crit_mh());
	}
	
	public AttackData get_dmg_revealing_strike(){
		Weapon w = s.getMh();
		double damage = w.getSpeed() * ( w.getDps() + s.getBuffed(Stat.ap) / 3.5);
		damage *= 0.8;
		double crit_damage = damage * this.getCritMultiplier();
		return new AttackData("revealing_strike",AttackType.PHYSICAL,damage, crit_damage, get_crit_mh());
	}
	
	public AttackData get_dmg_eviscerate(){
		return get_dmg_eviscerate(5);
	}
	
	public AttackData get_dmg_eviscerate(int cps){
		double damage = 1 + s.getBuffed(Stat.ap) * cps * 0.3203;
		if(s.getSettings().spec == Spec.COMBAT)
			damage *= 1.35;
		// perk combat improved eviscerate
		if(s.getSettings().spec == Spec.COMBAT && s.getSettings().player_level >= 100)
			damage *= 1.2;
		double crit_damage = damage * this.getCritMultiplier();
		return new AttackData("eviscerate",AttackType.PHYSICAL,damage, crit_damage, get_crit_mh());
	}
	
	public AttackData get_dmg_death_from_above(){
		return get_dmg_death_from_above(5);
	}
	// TODO final evi/envenom deals 50% more damage
	public AttackData get_dmg_death_from_above(int cps){
		double damage = 2./3. * s.getBuffed(Stat.ap);
		double crit_damage = damage * this.getCritMultiplier();
		
		return new AttackData("death_from_above",AttackType.PHYSICAL,damage, crit_damage, get_crit_mh());
	}
	
	public AttackData get_dmg_rupture(){
		return get_dmg_rupture(5);
	}
	
	public AttackData get_dmg_rupture(int cp){
		//double[] ap_scaling = {0.0, 0.329, 0.986, 1.973, 3.288 , 4.932}; 
		
		double damage = 0.0685 * s.getBuffed(Stat.ap);
		if(s.getSettings().spec == Spec.SUBTLETY)
			damage *= 1.1;
		if(s.getSettings().spec == Spec.COMBAT)
			damage *= 1.35;
		double crit_damage = damage * this.getCritMultiplier();
		return new AttackData("rupture",AttackType.BLEED, damage, crit_damage, get_crit_mh());
	}
	
	public AttackData get_dmg_mh(){
		Weapon w = s.getMh();
		double damage = w.getSpeed() * ( w.getDps() + s.getBuffed(Stat.ap) / 3.5);
		double crit_damage = damage * this.getCritMultiplier();
		return new AttackData("mh",AttackType.PHYSICAL, damage, crit_damage, get_crit_mh());
	}
	
	public AttackData get_dmg_oh(){
		Weapon w = s.getOh();
		double damage = w.getSpeed() * ( w.getDps() + s.getBuffed(Stat.ap) / 3.5);
		// offhand penalty due to dual wielding
		double mod = 0.5;
		damage *= mod;
		double crit_damage = damage * this.getCritMultiplier();
		return new AttackData("oh",AttackType.PHYSICAL, damage, crit_damage, get_crit_oh());
	}
	
	public AttackData get_dmg_ks_mh(){
		Weapon w = s.getMh();
		double damage = w.getSpeed() * w.getDps() + w.getType().getNormalizedSpeed() * s.getBuffed(Stat.ap) / 3.5;
		damage *= 1.5;
		if(s.hasSetBonus(SetBonus.T16_4PC))
			damage *= 1.49;
		double crit_damage = damage * this.getCritMultiplier();
		return new AttackData("ks_mh",AttackType.PHYSICAL,damage, crit_damage, get_crit_mh());
	}
	
	public AttackData get_dmg_ks_oh(){
		Weapon w = s.getOh();
		double damage = w.getSpeed() * w.getDps() + w.getType().getNormalizedSpeed() * s.getBuffed(Stat.ap) / 3.5;
		double mod = 0.5;
		damage *= mod;
		damage *= 1.5;
		if(s.hasSetBonus(SetBonus.T16_4PC))
			damage *= 1.49;
		double crit_damage = damage * this.getCritMultiplier();
		return new AttackData("ks_oh", AttackType.PHYSICAL, damage, crit_damage, get_crit_oh());
	}
	
	public AttackData get_dmg_main_gauche(){
		Weapon w = s.getOh();
		double damage = w.getSpeed() * w.getDps() + w.getType().getNormalizedSpeed() * s.getBuffed(Stat.ap) / 3.5;
		double mod = 0.5 * 1.5;
		damage *= mod;
		double crit_damage = damage * this.getCritMultiplier();
		return new AttackData("main_gauche", AttackType.PHYSICAL, damage, crit_damage, get_crit_mh());
	}
	
	public AttackData get_dmg_backstab(){
		Weapon w = s.getMh();
		double damage = w.getSpeed() * w.getDps() + w.getType().getNormalizedSpeed() * s.getBuffed(Stat.ap) / 3.5;
		double mod = 1.3;
		damage = damage * mod;
		if(s.hasSetBonus(SetBonus.T14_2PC))
			damage *= 1.10;
		if(w.getType() != WeaponType.DAGGER)
			damage = 0.0;
		// perk enhanced backstab
		if(s.getSettings().spec == Spec.SUBTLETY && s.getSettings().player_level >= 100) {
			damage *= 1.2;
		}
		double crit_damage = damage * this.getCritMultiplier();
		return new AttackData("backstab",AttackType.PHYSICAL, damage, crit_damage, get_crit_mh());
	}
	
	public AttackData get_dmg_ambush(){
		Weapon w = s.getMh();
		double damage = w.getSpeed() * w.getDps() + w.getType().getNormalizedSpeed() * s.getBuffed(Stat.ap) / 3.5;
		double mod = 2.5;
		if(w.getType() == WeaponType.DAGGER)
			mod *= 1.447;
		damage = damage * mod;
		// perk enhanced ambush
		if(s.getSettings().spec == Spec.SUBTLETY && s.getSettings().player_level >= 100) {
			damage *= 1.2;
		}
		double crit_damage = damage * this.getCritMultiplier();
		return new AttackData("ambush",AttackType.PHYSICAL, damage, crit_damage, get_crit_mh());
	}
	
	public AttackData get_dmg_hemorrhage(){
		Weapon w = s.getMh();
		double damage = w.getSpeed() * w.getDps() + w.getType().getNormalizedSpeed() * s.getBuffed(Stat.ap) / 3.5;
		double mod = 0.65;
		if(w.getType() == WeaponType.DAGGER)
			mod = 0.9425;
		damage = damage * mod;
		// perk enhanced hemorrhage
		if(s.getSettings().spec == Spec.SUBTLETY && s.getSettings().player_level >= 100) {
			damage *= 1.2;
		}
		double crit_damage = damage * this.getCritMultiplier();
		return new AttackData("hemorrhage",AttackType.PHYSICAL, damage, crit_damage, get_crit_mh());
	}
	
	public AttackData get_dmg_hemorrhage_dot(){
		AttackData hemo_direct = get_dmg_hemorrhage();
		double hemo_direct_crit_chance = hemo_direct.getCritChance();
		double hit = hemo_direct.getHitDamage() * ( 1 - hemo_direct_crit_chance);
		double crit = hemo_direct.getCritDamage() * hemo_direct_crit_chance;
		
		double damage = (hit + crit) / 8;
		double mod = 0.5;
		damage = damage * mod;
		double crit_damage = damage * this.getCritMultiplier();
		return new AttackData("hemorrhage_dot",AttackType.BLEED, damage, crit_damage, get_crit_mh());
	}
	
	public AttackData get_dmg_mutilate_mh(){
		Weapon w = s.getMh();
		double damage = w.getSpeed() * w.getDps() + w.getType().getNormalizedSpeed() * s.getBuffed(Stat.ap) / 3.5;
		double mod = 2.0;
		damage = damage * mod;
		if(w.getType() != WeaponType.DAGGER)
			damage = 0.0;
		double crit_damage = damage * this.getCritMultiplier();
		return new AttackData("mutilate_mh",AttackType.PHYSICAL, damage, crit_damage, get_crit_mh());
	}
	
	public AttackData get_dmg_mutilate_oh(){
		Weapon w = s.getMh();
		double damage = w.getSpeed() * w.getDps() + w.getType().getNormalizedSpeed() * s.getBuffed(Stat.ap) / 3.5;
		double mod = 2.0;
		mod *= 0.5; // offhand penalty
		damage = damage * mod;
		if(w.getType() != WeaponType.DAGGER)
			damage = 0.0;
		double crit_damage = damage * this.getCritMultiplier();
		return new AttackData("mutilate_oh",AttackType.PHYSICAL, damage, crit_damage, get_crit_oh());
	}
	
	public AttackData get_dmg_dispatch(){
		Weapon w = s.getMh();
		double damage = w.getSpeed() * w.getDps() + w.getType().getNormalizedSpeed() * s.getBuffed(Stat.ap) / 3.5;
		double mod = 3.15;
		damage = damage * mod;
		if(w.getType() != WeaponType.DAGGER)
			damage = 0.0;
		double crit_damage = damage * this.getCritMultiplier();
		return new AttackData("dispatch",AttackType.PHYSICAL, damage, crit_damage, get_crit_mh());
	}
	
	public AttackData get_dmg_envenom(){
		return get_dmg_envenom(5);
	}
	
	public AttackData get_dmg_envenom(int cps){
		double damage = (1 + s.getBuffed(Stat.ap) * 0.306) * cps;
		double crit_damage = damage * this.getCritMultiplier();
		
		return new AttackData("envenom",AttackType.MAGICAL,damage, crit_damage, get_crit_mh());
	}
	
	public AttackData get_dmg_venomous_wound(){
		double damage = 0.3200000 * s.getBuffed(Stat.ap);
		if(s.hasSetBonus(SetBonus.T14_2PC))
			damage *= 1.20;
		double crit_damage = damage * this.getCritMultiplier();
		return new AttackData("venomous_wound",AttackType.MAGICAL, damage, crit_damage, get_crit_mh());
	}

	public AttackData get_dmg_instant_poison(){
		// perk combat instant poison
		if(s.getSettings().spec == Spec.COMBAT && s.getSettings().player_level >= 100) {
			double damage = 0.264 * s.getBuffed(Stat.ap);
			double crit_damage = damage * this.getCritMultiplier();
			return new AttackData("instant_poison",AttackType.MAGICAL, damage, crit_damage, get_crit_mh());
		}
		double damage = 0.1287 * s.getBuffed(Stat.ap);
		double crit_damage = damage * this.getCritMultiplier();
		return new AttackData("instant_poison",AttackType.MAGICAL, damage, crit_damage, get_crit_mh());
	}

	public AttackData get_dmg_deadly_poison(){
		double damage = 0.25 * s.getBuffed(Stat.ap);
		double crit_damage = damage * this.getCritMultiplier();
		return new AttackData("deadly_poison",AttackType.MAGICAL, damage, crit_damage, get_crit_mh());
	}
	
	public AttackData get_dmg_capacitive_primal_diamond(){
		double damage = 280 + 0.75 * s.getBuffed(Stat.ap);
		double crit_damage = damage * this.getCritMultiplier();
		return new AttackData("capacitive_primal_diamond",AttackType.MAGICAL, damage, crit_damage, get_crit_mh());
	}
	
	public AttackData get_dmg_fury_of_xuen(){
		double damage = 10 * (1 + 0.2 * s.getBuffed(Stat.ap)); // every 0.3sec for 3 sec
		double crit_damage = damage * this.getCritMultiplier();
		return new AttackData("fury_of_xuen",AttackType.PHYSICAL, damage, crit_damage, get_crit_mh());
	}
	
	public AttackData get_dmg_touch_of_the_grave(){
		double damage = 8 * this.constant_scaling;
		double crit_damage = damage * this.getCritMultiplier();
		return new AttackData("touch_of_the_grave",AttackType.MAGICAL,damage, crit_damage, 0);
	}
	
	public AttackData get_dmg_mh_mark_of_the_shattered_hand() {
		double damage = 0.1675000042 * 37000.; // lvl 90, lvl100 = 170000.
		double crit_damage = damage * this.getCritMultiplier();
		return new AttackData("mh_mark_of_the_shattered_hand",AttackType.BLEED,damage, crit_damage, get_crit_mh());
	}
	
	public AttackData get_dmg_oh_mark_of_the_shattered_hand() {
		double damage = 0.1675000042 * 37000.; // lvl 90, lvl100 = 170000.
		double crit_damage = damage * this.getCritMultiplier();
		return new AttackData("oh_mark_of_the_shattered_hand",AttackType.BLEED,damage, crit_damage, get_crit_oh());
	}
	
	public double get_crit_mh(){
		double crit_chance = s.getCritPercent();
		return crit_chance;
	}
	
	public double get_crit_oh(){
		return get_crit_mh();
	}
	
	public AttackData get_dmg(String attack) {
		return get_dmg(attack, 0);
	}
	
	public double getCritMultiplier(){
		double amp = 1;
		ProcInfo procInfo = null;
		for(String prefix : Procs.trinket_prefixes){
			if(s.hasProc(prefix+"thoks_tail_tip"))
				procInfo = s.getProcInfo(prefix+"thoks_tail_tip");
		}
		if(procInfo != null){
			ProcData procData = s.getProcData().get(procInfo.getProcName());
			// or 0.0008850000 / 0.0017700000
			double scale = Cycle.get_scaling_value(procData,procInfo.getUpgradeLevel());
			double res = Math.round(scale * 0.0017700000) / 100;
			amp += res;
		}
		double crit_damage = 2.0 * amp;
		if(s.getRace() == Race.dwarf)
			crit_damage *= 1.02;
		return crit_damage;
	}

	public AttackData get_dmg(String attack, int cps) {
		Method method;
		AttackData value = null;
		try {
			if(cps > 0){
				method = this.getClass().getMethod( "get_dmg_" + attack , int.class );
				value = (AttackData) method.invoke( this, cps);
			}else{
				method = this.getClass().getMethod( "get_dmg_" + attack );
				value = (AttackData) method.invoke( this );
			}
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			System.err.println("Error: The method " + e.getMessage() + " needs to be defined.");
			System.exit(1);
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		}
		return value;
	}
}
